package edu.whu.tao.util;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

/**
 * 
 * <P>RSA加密解密</P>
 * @author 陶焕(13294175866)
 * @date 2016年6月20日 下午8:17:48
 */
public class RSAUtils {

	private RSAUtils(){
		
	}
	
	private static class RSAUtilsFactory {
		private static final RSAUtils instance = new RSAUtils();
	}
	
	public static RSAUtils getInstance() {
		return RSAUtilsFactory.instance;
	}
	

	public static byte[][] genKeyPairBytes(int keySize)
			throws NoSuchAlgorithmException {
		byte[][] keyPairBytes = new byte[2][];

		KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA",
				new org.bouncycastle.jce.provider.BouncyCastleProvider());
		gen.initialize(keySize, new SecureRandom());
		KeyPair pair = gen.generateKeyPair();

		keyPairBytes[0] = pair.getPrivate().getEncoded();
		keyPairBytes[1] = pair.getPublic().getEncoded();

		return keyPairBytes;
	}

	public static String[] genKeyPairStr(int keySize)
			throws NoSuchAlgorithmException {
		byte[][] keyPairBytes = genKeyPairBytes(keySize);
		String[] keyPairs = new String[2];

		keyPairs[0] = Base64.byteArrayToBase64(keyPairBytes[0]);
		keyPairs[1] = Base64.byteArrayToBase64(keyPairBytes[1]);

		return keyPairs;
	}
	
	public static KeyPair genKeyPair(int keySize)
			throws NoSuchAlgorithmException {
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
				new org.bouncycastle.jce.provider.BouncyCastleProvider());
		keyPairGen.initialize(keySize, new SecureRandom());
		KeyPair pair = keyPairGen.generateKeyPair();

		return pair;
	}

	/**
	 * 加密
	 * 
	 * @param key 加密的密钥  私钥
	 * @param data 待加密的明文数据
	 * @return 加密后的数据
	 * @throws Exception
	 */
	public static byte[] encrypt(Key key, byte[] data) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(Cipher.ENCRYPT_MODE, key);
			// 获得加密块大小，如:加密前数据为128个byte，而key_size=1024 加密块大小为127 byte,加密后为128个byte;
			// 因此共有2个加密块，第一个127 byte第二个为1个byte
			int blockSize = cipher.getBlockSize();
			int outputSize = cipher.getOutputSize(data.length);// 获得加密块加密后块大小
			int leavedSize = data.length % blockSize;
			int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
			byte[] raw = new byte[outputSize * blocksSize];
			int i = 0;
			while (data.length - i * blockSize > 0) {
				if (data.length - i * blockSize > blockSize)
					cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
				else
					cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
				// 这里面doUpdate方法不可用，查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到ByteArrayOutputStream中
				// ，而最后doFinal的时候才将所有的byte[]进行加密，可是到了此时加密块大小很可能已经超出了OutputSize所以只好用dofinal方法。
				i++;
			}
			return raw;
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}
	}

	public static String encrypt(byte[] keyBytes, String plainText)
			throws Exception {
		PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory factory = KeyFactory.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider());
		PrivateKey privateKey = factory.generatePrivate(spec);
		byte[] encryptedBytes = encrypt(privateKey,plainText.getBytes("UTF-8"));
		String encryptedString = Base64.byteArrayToBase64(encryptedBytes);

		return encryptedString;
	}
	
	public static String encrypt(String keyStr, String plainText)
			throws Exception {
		byte[] keyBytes = Base64.base64ToByteArray(keyStr);
		String encryptedString = encrypt(keyBytes,plainText);
		return encryptedString;
	}
	
	/**
	 * 解密
	 * 
	 * @param key 解密的密钥  公钥
	 * @param raw 已经加密的数据
	 * @return 解密后的明文
	 * @throws Exception
	 */
	public static byte[] decrypt(Key key, byte[] raw) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
			cipher.init(cipher.DECRYPT_MODE, key);
			int blockSize = cipher.getBlockSize();
			ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
			int j = 0;
			while (raw.length - j * blockSize > 0) {
				bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
				j++;
			}
			return bout.toByteArray();
		} catch (Exception e) {
			throw new Exception(e.getMessage());
		}
	}

	public static String decrypt(byte[] keyBytes, String plainText)
			throws Exception {
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(
				keyBytes);
		KeyFactory factory = KeyFactory.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider());
		PublicKey publicKey = factory.generatePublic(x509KeySpec);
		byte[] cipherBytes = Base64.base64ToByteArray(plainText);
		byte[] decryptedBytes = decrypt(publicKey,cipherBytes);
		String decryptedString = new String(decryptedBytes, "UTF-8");

		return decryptedString;
	}
	
	public static String decrypt(String keyStr, String plainText)
			throws Exception {
		byte[] keyBytes = Base64.base64ToByteArray(keyStr);
		String decryptedString = decrypt(keyBytes,plainText);

		return decryptedString;
	}
	

	/**
	 * 测试
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {

		RSAUtils rsa = RSAUtils.getInstance();
		String str = "陶焕";
		String[] keys = rsa.genKeyPairStr(1024);
		String privateKey = keys[0];
		String publicKey = keys[1];
		LogUtils.println("私钥", privateKey);
		LogUtils.println("公钥", publicKey);
		LogUtils.println("加密后", rsa.encrypt(privateKey, str));
		LogUtils.println("解密后", rsa.decrypt(publicKey, rsa.encrypt(privateKey, str)));
	}
}
